
package jogs.net.metaserver;

import jogs.core.*; 

import java.sql.*;
import java.util.*; 
import java.io.*; 
import java.net.*; 

/**
 * This is the main server that takes care of collecting and aggregating
 * all the useful information from the main game servers and game room 
 * servers. 
 */
public class MetaServer
{
  private Properties settings; 
  private int port; 
  private ServerSocket serverSocket;
  private ArrayList<MSClientHandler> clients;

  private String mspw; 

  private String dbhost, dbuser, dbpass, dbname; 
  private Connection dbconn;

  private HashMap<String,GameRoomServerInfo> tag2grsi;
  //private HashMap<String,String> userpasses;

  /** 
   * Static methods
   */

  private static void exit_usage()
  {
    System.out.println("Usage: MetaServer <settings file>"); 
    System.exit(-1); 
  }

  public static void main(String[] args)
  {
    if (args.length < 1) 
      exit_usage(); 

    MetaServer ms = new MetaServer(args[0]); 
    ms.start(); 
  }

  /**
   * Class Methods
   */

  public MetaServer(String propFile)
  {
    clients = new ArrayList<MSClientHandler>(); 
    parseSettings(propFile);   
    tag2grsi = new HashMap<String,GameRoomServerInfo>();   
  }

  public synchronized void broadcast(String msg)
  {
    for (MSClientHandler client : clients)
      if (!client.disconnected()) client.send(msg); 
  }

  public void clientDisconnect(MSClientHandler msch)
  {
  	this.clients.remove(msch);
  	this.broadcast("@DISCONNECTED:" + msch.getUsername());
  }
  
  public String getRoomInfo()
  {
    String info = ""; 

    for (GameRoomServerInfo grsi : tag2grsi.values())
      info += (grsi.name + " (" + grsi.clientCount + ")###"); 

    return info;     
  }

  public String getUserList()
  {
    String info = "";    
    for (MSClientHandler client : clients)
    {
      if (!client.disconnected())
        info += client.getUsername() + ",";
    }
    
    int len = info.length();
    if (len > 0)
      info = info.substring(0, len-1); 

    return info; 
  }

  public synchronized void updateRoomCount(GameRoomServerInfo grsi)
  {
  	broadcast("@ROOMCOUNT:" + grsi.name + "," + grsi.clientCount);
  }
  
  public String handleJoin(String line, String username)
  {
    String[] parts = line.split(" "); 
    String room = parts[1]; 
    String info = null;

    for (GameRoomServerInfo grsi : tag2grsi.values())
    {
      if (room.equals(grsi.name))
      {
        String keypass = Helpers.genKeyPass(128); 
        try
        {
          grsi.out.println("@ADDKEYPASS:" + username + "," + keypass); 
          info = room + "," + grsi.host + "," + grsi.port + "," + keypass; 

          // Wait for response
          grsi.waitForCommand();  
          String response = grsi.getLastCommand(); 

          if (!response.equals("@OK_ADDKEYPASS"))
          {
            System.out.println("Got invalid response from GRS: " + response); 
            return null; 
          }
        }
        catch(Exception e)
        {
          e.printStackTrace();
        }

        break;
      }
    }

    return info; 
  }

  public boolean authenticate(String username, String password)
  {
    try 
    { 
      System.out.println("Authenticating... "); 

      Statement s = dbconn.createStatement ();
      s.executeQuery ("SELECT id, username, password FROM users WHERE username='" + username + "'");
      ResultSet rs = s.getResultSet ();
      int count = 0;

      while (rs.next())
      {
        String qusername = rs.getString("username");
        String qpassword = rs.getString("password");

        //System.out.println(qusername + " " + qpassword); 

        if (!(qusername.equals(username) && qpassword.equals(password)))
          return false; 

        ++count;
      }

      if (count < 1)
        return false; 
    
      if (count > 1) 
        Helpers.fatalError("Rows in authentication = " + count); 

      rs.close ();
      s.close ();

      //System.out.println (count + " rows were retrieved");
      System.out.println(username + " logged in."); 

      return true;
    }
    catch(Exception e)
    {
      e.printStackTrace(); 
    }

    return false; 
  }

  private String getSetting(String key)
  {
    String setting = settings.getProperty(key);

    if (setting == null)
     Helpers.fatalError("Required setting missing in settings: " + key); 

    return setting; 
  }

  private void parseSettings(String propFile)
  {
    try
    {
      System.out.println("Parsing settings...");
      FileInputStream in = new FileInputStream(propFile); 
      settings = new Properties(); 
      settings.load(in); 

      port = Integer.parseInt(settings.getProperty("port")); 

      dbhost = getSetting("dbhost"); 
      dbuser = getSetting("dbuser"); 
      dbpass = getSetting("dbpass"); 
      dbname = getSetting("dbname"); 

      mspw = getSetting("mspw"); 
    }
    catch(Exception e)
    {
      Helpers.fatalError(e); 
    }
  }

  private void dbconnect()
  {
    try
    {
      String url = "jdbc:mysql://" + dbhost + "/" + dbname;
      Class.forName("com.mysql.jdbc.Driver").newInstance();
      dbconn = DriverManager.getConnection(url, dbuser, dbpass);
      System.out.println ("Database connection established");
    }
    catch(Exception e)
    { 
      Helpers.fatalError(e);
    }
  }

  private void connect_grs()
  {
    Enumeration keys = settings.propertyNames(); 

    while (keys.hasMoreElements())
    {
      String key = (String)keys.nextElement(); 

      if (!key.startsWith("grserver."))
        continue; 

      String tag = key.substring(9); 
      String value = settings.getProperty(key);
      String[] parts = value.split(":"); 
      String host = parts[0];
      int port = Integer.parseInt(parts[1]); 

      System.out.print(tag + " " + host + ":" + port + " ... "); 
      
      try
      {
        Socket socket = new Socket(host, port); 
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); 
        PrintStream out = new PrintStream(socket.getOutputStream()); 

        in.readLine(); // AUTHENTICATE

        out.println("#@metaserver@#"); 
        out.println(mspw); 

        String response = in.readLine();
        System.out.print(response); 

        if (!response.equals("LOGINSUCC"))
        {
          System.out.println("");
          continue;
        }

        out.println("@GET_CLIENT_COUNT:");
        int clientCount = Integer.parseInt(in.readLine()); 
        
        out.println("@GET_NAME:");
        String name = in.readLine(); 

        System.out.println(" " + name + " " + clientCount);

        GameRoomServerInfo grsi = new GameRoomServerInfo(host, port, socket, in, out, name, clientCount, this); 
        grsi.start();
        tag2grsi.put(tag, grsi); 
        
      }
      catch(Exception e)
      {
        System.out.println(e);
      }
    }
  }

  private void start()
  {
    try 
    {
      System.out.println("Connecting to database."); 
      dbconnect();

      System.out.println("Connecting to game room servers."); 
      connect_grs(); 
      if (tag2grsi.size() < 1)
        Helpers.fatalError("No available game room servers! Can't start metaserver."); 

      System.out.println("Binding socket to port " + port); 
      serverSocket = new ServerSocket(port); 

      System.out.println("Accepting clients..."); 

      while(true)
      {
        Socket client = serverSocket.accept(); 

        System.out.println("Client accepted. Creating and starting client handler. "); 

        MSClientHandler mscl = new MSClientHandler(client, this); 
        mscl.start(); 
        clients.add(mscl); 
      }
    }
    catch(Exception e)
    {
      e.printStackTrace(); 
    }
  }
}

